const Proxy = require('../../proxy')
const webDataUtil = require('../../util/webDataUtil')
const reqlib = require('app-root-path').require
const btbAs = reqlib('/app/common/btb')
const appConfig = reqlib('/config/index')
const commonDataCreate = require('./utils/commonDataCreate')
const AnalysisInfoFlag = reqlib('/app/analysis/analysisInfoFlag')
const AccountMachine = reqlib('/app/analysis/accountMachine')
const BuySellMachine = reqlib('/app/analysis/buySellMachine')
const Performance = reqlib('/app/common/performance')
const numberUtil = reqlib('/app/util/numberUtil')
const MarketMachine = reqlib('/app/analysis/marketMachine')
const moment = require('moment')

const BtbWarnProxy = Proxy.BtbWarn
const Dictionary = Proxy.Dictionary
const QuoteVolumeProxy = Proxy.QuoteVolume

function parseValue (val) {
  return parseFloat(val || 0) || 0
}

const checkWarnBtb = async (time) => {
  const lastList = await BtbWarnProxy.find({
    time_str: time
  })
  if (lastList && lastList[0]) {
    return lastList[0]
  } else {
    return false
  }
}

const getWithKlineList = async (item) => {
  const key = item.code
  const klineResult = await Promise.all([
    webDataUtil.getFuturesKLinesAll({
      name: `${key}`,
      interval: '15m',
      count: 500
    }),
    webDataUtil.getFuturesKLinesAll({
      name: `${key}`,
      interval: '1h',
      count: 500
    }),
    webDataUtil.getFuturesKLinesAll({
      name: `${key}`,
      interval: '4h',
      count: 500
    }),
    webDataUtil.getFuturesKLinesAll({
      name: `${key}`,
      interval: '1d',
      count: 500
    })
  ])
  item.symbol = `${item.code}USDT`
  item.min_qty = item.f_min_qty
  item.max_qty = item.f_max_qty
  item.step_size = item.f_step_size
  item.min_notional = item.f_min_notional
  item.tick_size = item.f_tick_size
  item.klineList15m = klineResult[0]
  item.klineList1h = klineResult[1]
  item.klineList4h = klineResult[2]
  item.klineList1d = klineResult[3]
}

const analysisBtbItem = (item) => {
// 慢是因为分析2000天的，10个就20000天
  item.analysisInfoList15m = btbAs.getItemInfoList(item.klineList15m)
  item.analysisInfoList1h = btbAs.getItemInfoList(item.klineList1h)
  item.analysisInfoList4h = btbAs.getItemInfoList(item.klineList4h)
  item.analysisInfoList1d = btbAs.getItemInfoList(item.klineList1d)
  // 都转为从近到远
  item.analysisInfoList15m.reverse()
  item.analysisInfoList1h.reverse()
  item.analysisInfoList4h.reverse()
  item.analysisInfoList1d.reverse()
  item.analysisInfoList15m.forEach((v, index) => {
    commonDataCreate.asInfoExtend(item.analysisInfoList15m, v, index)
  })
  item.analysisInfoList1h.forEach((v, index) => {
    commonDataCreate.asInfoExtend(item.analysisInfoList1h, v, index)
  })
  item.analysisInfoList4h.forEach((v, index) => {
    commonDataCreate.asInfoExtend(item.analysisInfoList4h, v, index)
  })
  item.analysisInfoList1d.forEach((v, index) => {
    commonDataCreate.asInfoExtend(item.analysisInfoList1d, v, index)
  })
  item.klineList15m.reverse()
  item.klineList1h.reverse()
  item.klineList4h.reverse()
  item.klineList1d.reverse()
  item.analysisInfoFlag15m = new AnalysisInfoFlag({
    analysisInfoList: item.analysisInfoList15m,
    type: '15m',
    code: item.code
  })
  item.close = item.analysisInfoFlag15m.nowEmaInfo.close
  item.analysisInfoFlag1h = new AnalysisInfoFlag({
    analysisInfoList: item.analysisInfoList1h,
    type: '1h',
    code: item.code
  })
  item.analysisInfoFlag4h = new AnalysisInfoFlag({
    analysisInfoList: item.analysisInfoList4h,
    type: '4h',
    code: item.code
  })
  item.analysisInfoFlag1d = new AnalysisInfoFlag({
    analysisInfoList: item.analysisInfoList1d,
    type: '1d',
    code: item.code
  })
}

const getMarketBtbList = async (fsymbols) => {
  const futuresDailyRes = await webDataUtil.binance('getFuturesDaily')
  const futuresDailyData = futuresDailyRes.data || {}
  const list = []
  for (let key in futuresDailyData) {
    if (
      key.indexOf('USDT') !== -1 &&
      key.indexOf('USDT') !== 0 &&
      key.endsWith('USDT')
    ) {
      if (
        key.indexOf('DOWN') === -1 &&
        ['ETHUSDT', 'BTCUSDT'].indexOf(key) === -1
      ) {
        const itemData = futuresDailyData[key]
        const code = itemData.symbol.replace('USDT', '')
        itemData.code = code
        itemData.priceChangePercent = parseFloat(itemData.priceChangePercent)
        const fexInfo = btbAs.getEXInfo(fsymbols, code)
        if (fexInfo.status === 'TRADING') {
          list.push(itemData)
        }
      }
    }
  }
  // 按成交量排序
  list.sort((a, b) => {
    // 返回1，a到b右边
    // 返回-1，b到a右边
    const aq = parseFloat(a.quoteVolume)
    const bq = parseFloat(b.quoteVolume)
    return bq - aq
  })
  return list
}

function CountSumModel () {
  this.allUp = 0
  this.allDown = 0
}
CountSumModel.prototype = {
  count (analysisInfo15m, analysisInfo1h) {
    if (
      analysisInfo15m.ema5W10A20Days > 0 &&
      analysisInfo15m.macdUpDays > 0
    ) {
      if (
        analysisInfo1h.ema5W10A20Days > 0 &&
        analysisInfo1h.macdUpDays > 0
      ) {
        this.allUp++
      }
    }
    if (
      analysisInfo15m.ema5W10B20Days > 0 &&
      analysisInfo15m.macdDownDays > 0
    ) {
      if (
        analysisInfo1h.ema5W10B20Days > 0 &&
        analysisInfo1h.macdDownDays > 0
      ) {
        this.allDown++
      }
    }
  },
  getInfo () {
    return {
      allUp: this.allUp,
      allDown: this.allDown
    }
  }
}

function CountModel () {
  this.count5A10 = 0
  this.count5A10Days = 0
  this.count5W10A20 = 0
  this.count5W10A20Days = 0
  this.count5A10A20 = 0
  this.count5A10A20Days = 0
  this.count5B10 = 0
  this.count5B10Days = 0
  this.count5W10B20 = 0
  this.count5W10B20Days = 0
  this.count5B10B20 = 0
  this.count5B10B20Days = 0
  this.countMacdUp = 0
  this.countMacdUpDays = 0
  this.countMacdDown = 0
  this.countMacdDownDays = 0
  this.countKdjUp = 0
  this.countKdjUpDays = 0
  this.countKdjDown = 0
  this.countKdjDownDays = 0
  this.countLongKong = 0
  this.countKong = 0
  this.countLongDuo = 0
  this.countDuo = 0
  this.countUp = 0
  this.countDown = 0
}
CountModel.prototype = {
  count (analysisInfo) {
    if (analysisInfo.ema5W10A20Days > 0) {
      this.count5W10A20++
      this.count5W10A20Days += analysisInfo.ema5W10A20Days
    }
    if (analysisInfo.ema5A10Days > 0) {
      this.count5A10++
      this.count5A10Days += analysisInfo.ema5A10Days
    }
    if (analysisInfo.ema5A10A20Days > 0) {
      this.count5A10A20++
      this.count5A10A20Days += analysisInfo.ema5A10A20Days
    }
    if (analysisInfo.ema5B10Days > 0) {
      this.count5B10++
      this.count5B10Days += analysisInfo.ema5B10Days
    }
    if (analysisInfo.ema5W10B20Days > 0) {
      this.count5W10B20++
      this.count5W10B20Days += analysisInfo.ema5W10B20Days
    }
    if (analysisInfo.ema5B10B20Days > 0) {
      this.count5B10B20++
      this.count5B10B20Days += analysisInfo.ema5B10B20Days
    }
    if (analysisInfo.macdUpDays > 0) {
      this.countMacdUp++
      this.countMacdUpDays += analysisInfo.macdUpDays
    }
    if (analysisInfo.macdDownDays > 0) {
      this.countMacdDown++
      this.countMacdDownDays += analysisInfo.macdDownDays
    }
    if (analysisInfo.kdjUpDays > 0) {
      this.countKdjUp++
      this.countKdjUpDays += analysisInfo.kdjUpDays
    }
    if (analysisInfo.kdjDownDays > 0) {
      this.countKdjDown++
      this.countKdjDownDays += analysisInfo.kdjDownDays
    }
    if (analysisInfo.isLongKong) {
      this.countLongKong++
    }
    if (analysisInfo.isLongDuo) {
      this.countLongDuo++
    }
    if (analysisInfo.isKong) {
      this.countKong++
    }
    if (analysisInfo.isDuo) {
      this.countDuo++
    }
    if (analysisInfo.netChangeRatio > 0) {
      this.countUp++
    }
    if (analysisInfo.netChangeRatio < 0) {
      this.countDown++
    }
  },
  getInfo () {
    return {
      count5A10: this.count5A10,
      count5A10Days: parseInt(this.count5A10Days / this.count5A10 || 1),
      count5W10A20: this.count5W10A20,
      count5W10A20Days: parseInt(this.count5W10A20Days / this.count5W10A20 || 1),
      count5A10A20: this.count5A10A20,
      count5A10A20Days: parseInt(this.count5A10A20Days / this.count5A10A20 || 1),
      count5B10: this.count5B10,
      count5B10Days: parseInt(this.count5B10Days / this.count5B10 || 1),
      count5W10B20: this.count5W10B20,
      count5W10B20Days: parseInt(this.count5W10B20Days / this.count5W10B20 || 1),
      count5B10B20: this.count5B10B20,
      count5B10B20Days: parseInt(this.count5B10B20Days / this.count5B10B20 || 1),
      countMacdUp: this.countMacdUp,
      countMacdUpDays: parseInt(this.countMacdUpDays / this.countMacdUp || 1),
      countMacdDown: this.countMacdDown,
      countMacdDownDays: parseInt(this.countMacdDownDays / this.countMacdDown || 1),
      countKdjUp: this.countKdjUp,
      countKdjUpDays: parseInt(this.countKdjUpDays / this.countKdjUp || 1),
      countKdjDown: this.countKdjDown,
      countKdjDownDays: parseInt(this.countKdjDownDays / this.countKdjDown || 1),
      countLongDuo: this.countLongDuo,
      countLongKong: this.countLongKong,
      countDuo: this.countDuo,
      countKong: this.countKong,
      countUp: this.countUp,
      countDown: this.countDown
    }
  }
}

const countAvNetValueChange = (list) => {
  const newList = [...list]
  // 大的在左边
  newList.sort((a, b) => {
    const aq = parseFloat(a.priceChangePercent)
    const bq = parseFloat(b.priceChangePercent)
    return bq - aq
  })
  let maxSum = 0
  let maxCount = 0
  let minSum = 0
  let minCount = 0
  newList.forEach((v, index) => {
    v.priceChangeRank = index + 1
    if (index >= 2 && index < 52) {
      maxSum += v.priceChangePercent
      maxCount++
      v.max50 = true
    }
    if (index >= (newList.length - 52) && index < (newList.length - 2)) {
      minSum += v.priceChangePercent
      minCount++
      v.min50 = true
    }
  })
  return {
    max50AvRate: numberUtil.keepTwoDecimals(maxSum / maxCount),
    min50AvRate: numberUtil.keepTwoDecimals(minSum / minCount)
  }
}

const getDictionaryInfo = async () => {
  const preFind = await Dictionary.findOne({
    key: 'preFindBtbResult'
  })
  let preFindBtbResult = {}
  if (preFind) {
    try {
      preFindBtbResult = JSON.parse(preFind.value)
    } catch (e) {
      preFindBtbResult = {}
    }
  }
  return {
    preFindBtbResult
  }
}

const getRecentMarketInfo = async () => {
  const list = await QuoteVolumeProxy.find({}, {
    skip: 0,
    limit: 200,
    sort: '-time'
  }) || []
  return list.map((v) => {
    const detail = v.detail || {}
    return {
      ...detail
    }
  })
}

// 找币会找出10只，然后你交易基本上就只交易5只
module.exports = async function (ctx) {
  const p = new Performance()
  p.start()
  const dictionaryInfo = await getDictionaryInfo()
  const marketInfoList = await getRecentMarketInfo()
  let preFindBtbResult = dictionaryInfo.preFindBtbResult
  // 合约
  let fres = await webDataUtil.binance('getFuturesExchangeInfo')
  let fresData = fres.data || {}
  let fsymbols = fresData.symbols || []
  const accountMachine = new AccountMachine({
    // 这边设置多少都没关系
    btbCount: 5,
    marketInfoList: marketInfoList || []
  })
  await accountMachine.refresh()
  let marketBtbList = await getMarketBtbList(fsymbols)
  const abnormalVolatilityList = []
  marketBtbList.forEach((v) => {
    const priceChangePercent = parseFloat(v.priceChangePercent)
    if (Math.abs(priceChangePercent) > 60) {
      // abnormalVolatilityList.push({
      //   code: v.code,
      //   type: '24h',
      //   priceChangePercent: v.priceChangePercent
      // })
    }
  })
  const maxMin50AvRateInfo = countAvNetValueChange(marketBtbList)
  let quoteVolumeSum = 0
  let marketPriceChangePercent = 0
  for (let i = 0; i < marketBtbList.length; i++) {
    const item = marketBtbList[i]
    if (i < 100) {
      quoteVolumeSum += parseFloat(numberUtil.keepFourDecimals(parseFloat(item.quoteVolume) / 100000000))
      marketPriceChangePercent += item.priceChangePercent
    }
  }
  quoteVolumeSum = numberUtil.keepFourDecimals(quoteVolumeSum)
  let len = 0
  marketBtbList = marketBtbList.filter((v) => {
    const fexInfo = btbAs.getEXInfo(fsymbols, v.code)
    const minNotional = (parseValue(fexInfo.notional) || 0)
    if (
      minNotional <= appConfig.btb.minNotionalLine &&
      (
        // 一千万
        (parseFloat(v.quoteVolume) / 100000000) > appConfig.btb.minQuoteVolume ||
        (
          (parseFloat(v.quoteVolume) / 50000000) > appConfig.btb.minQuoteVolume &&
          (
            (v.min50 && v.priceChangePercent <= -15) ||
            (v.max50 && v.priceChangePercent >= 15)
          )
        )
      ) &&
      // 请求数量不能太多
      len < appConfig.btb.maxRequestNumber
    ) {
      len++
      return true
    }
    return false
  })
  fres = null
  fresData = null
  fsymbols = null
  let canOpenListRaw = []
  const countModel15m = new CountModel()
  const countModel1h = new CountModel()
  const countModel4h = new CountModel()
  const countModel1d = new CountModel()
  const countSumModel = new CountSumModel()
  let asCount = 0
  const errorList = []
  const newHighLowInfo = {
    newHighCount: 0,
    newHighList: [],
    newLowCount: 0,
    newLowList: []
  }
  const k15mInfo = {
    sumNetChangeRatio: 0,
    sumLowOpenRate: 0,
    sumHighOpenRate: 0,
    upCount: 0,
    downCount: 0
  }
  for (let i = 0; i < marketBtbList.length; i++) {
    const item = marketBtbList[i]
    try {
      await getWithKlineList(item)
      analysisBtbItem(item)
      item.buySellMachine = new BuySellMachine({
        realBuySell: false,
        preFind: true,
        btbItem: item,
        futuresAccount: {},
        symbolAccount: {},
        marketInfo: preFindBtbResult,
        marketInfoList: marketInfoList || []
      })
      // 执行
      item.buySellMachine.execute()
      const preInfo = item.buySellMachine.getPreInfo()
      if (item.buySellMachine.getIfAddInPollResult()) {
        canOpenListRaw.push({
          code: item.code,
          ...preInfo
        })
      }
      const analysisInfo15m = item.buySellMachine.analysisInfoFlag15m.flagInfo || {}
      const analysisInfo1h = item.buySellMachine.analysisInfoFlag1h.flagInfo || {}
      const analysisInfo4h = item.buySellMachine.analysisInfoFlag4h.flagInfo || {}
      const analysisInfoFlag1d = item.buySellMachine.analysisInfoFlag1d.flagInfo || {}
      countModel15m.count(analysisInfo15m)
      countModel1h.count(analysisInfo1h)
      countModel4h.count(analysisInfo4h)
      countModel1d.count(analysisInfoFlag1d)
      countSumModel.count(analysisInfo15m, analysisInfo1h)
      const warnInfo = item.buySellMachine.ifWarnAbnormal()
      if (warnInfo) {
        abnormalVolatilityList.push(warnInfo)
      }
      if (item.buySellMachine.indexFlagInfo && item.buySellMachine.indexFlagInfo.isIndexCloseNewHigh) {
        newHighLowInfo.newHighCount++
        newHighLowInfo.newHighList.push(item.code)
      }
      if (item.buySellMachine.indexFlagInfo && item.buySellMachine.indexFlagInfo.isIndexCloseNewLow) {
        newHighLowInfo.newLowCount++
        newHighLowInfo.newLowList.push(item.code)
      }
      k15mInfo.sumNetChangeRatio += analysisInfo15m.netChangeRatio
      k15mInfo.sumLowOpenRate += numberUtil.countDifferenceRate(analysisInfo15m.low, analysisInfo15m.open)
      k15mInfo.sumHighOpenRate += numberUtil.countDifferenceRate(analysisInfo15m.high, analysisInfo15m.open)
      if (analysisInfo15m.netChangeRatio > 0) {
        k15mInfo.upCount++
      } else {
        k15mInfo.downCount++
      }
      asCount++
    } catch (e) {
      console.log(e)
      errorList.push(e)
    }
    marketBtbList[i] = null
    p.print(`${item.code}-结束`)
  }
  const countModel15mInfo = countModel15m.getInfo()
  const countModel1hInfo = countModel1h.getInfo()
  const countModel4hInfo = countModel4h.getInfo()
  const countModel1dInfo = countModel1d.getInfo()
  const countSumModelInfo = countSumModel.getInfo()
  let onlyKong = btbAs.getIfOnlyKong(asCount, countModel1hInfo, countModel15mInfo)
  let onlyDuo = btbAs.getIfOnlyDuo(asCount, countModel1hInfo, countModel15mInfo)
  let likeKong = btbAs.getIfLikeKong(asCount, countModel1hInfo, countModel15mInfo)
  let likeDuo = btbAs.getIfLikeDuo(asCount, countModel1hInfo, countModel15mInfo)
  let disabledBuySell = btbAs.getIfDisabledBuySell(asCount, countModel1hInfo, countModel15mInfo)
  let attack15m = btbAs.getIfAttack15m(asCount, countModel1hInfo, countModel15mInfo)
  let crash15m = btbAs.getIfCrash15m(asCount, countModel1hInfo, countModel15mInfo)
  let canOpenList = canOpenListRaw
  canOpenList = canOpenList.filter((v) => {
    return v.buy || v.sell || v.addPool
  })
  p.print(`全部结束-${marketBtbList.length}个`)
  const resultInfo = {
    marketBtbListLen: marketBtbList.length,
    asCount,
    canOpenList: canOpenList.map((v) => {
      return v.code
    }),
    canOpenListRaw: canOpenList,
    countModel15m: countModel15mInfo,
    countModel1h: countModel1hInfo,
    countModel4h: countModel4hInfo,
    countModel1d: countModel1dInfo,
    countSumModelInfo: countSumModelInfo,
    onlyKong,
    onlyDuo,
    likeKong,
    likeDuo,
    disabledBuySell,
    attack15m,
    crash15m,
    attack15mKdjDown: (attack15m && ((countModel15mInfo.countKdjUp / asCount) <= 0.35)),
    attack15mKdjGood: (attack15m && ((countModel15mInfo.countKdjUp / asCount) >= 0.75)),
    crash15mKdjUp: (crash15m && ((countModel15mInfo.countKdjDown / asCount) <= 0.35)),
    crash15mKdjGood: (crash15m && ((countModel15mInfo.countKdjDown / asCount) >= 0.75)),
    likeDuoKdjDown: (likeDuo && ((countModel15mInfo.countKdjUp / asCount) <= 0.35)),
    likeDuoKdjGood: (likeDuo && ((countModel15mInfo.countKdjUp / asCount) >= 0.75)),
    likeKongKdjUp: (likeKong && ((countModel15mInfo.countKdjDown / asCount) <= 0.35)),
    likeKongKdjGood: (likeKong && ((countModel15mInfo.countKdjDown / asCount) >= 0.75)),
    quoteVolumeSum,
    abnormalVolatilityList,
    marketPriceChangePercent: numberUtil.keepTwoDecimals(marketPriceChangePercent / 100),
    ...maxMin50AvRateInfo,
    ...newHighLowInfo,
    k15mNetChangeRatio: numberUtil.keepTwoDecimals(k15mInfo.sumNetChangeRatio / asCount),
    k15mLowOpenRate: numberUtil.keepTwoDecimals(k15mInfo.sumLowOpenRate / asCount),
    k15mHighOpenRate: numberUtil.keepTwoDecimals(k15mInfo.sumHighOpenRate / asCount),
    k15mUpCount: k15mInfo.upCount,
    k15mDownCount: k15mInfo.downCount,
    errorList: errorList.map((err) => {
      return err.msg || err.message || ''
    }).filter((v) => {
      return !!(v)
    })
  }
  btbAs.getAnalysisMarketInfo(resultInfo)
  const marketMachine = new MarketMachine({
    marketInfo: resultInfo
  })
  const marketFlagInfo = marketMachine.getAllFlag()
  resultInfo.isMarketLittleGoodUp = marketFlagInfo.isMarketLittleGoodUp
  resultInfo.isMarketLittleBadDown = marketFlagInfo.isMarketLittleBadDown
  const futuresAccountInfo = accountMachine.getFuturesAccount()
  const quoteVolumeDetailData = {
    ...resultInfo,
    canOpenListRaw: '',
    abnormalVolatilityList: '',
    totalMarginBalance: futuresAccountInfo.totalMarginBalance
  }
  const volumeUpdateInfo = {
    value: quoteVolumeSum,
    count: marketBtbList.length,
    total_margin_balance: futuresAccountInfo.totalMarginBalance,
    duo_count: futuresAccountInfo.duoCount,
    kong_count: futuresAccountInfo.kongCount,
    duo_usdt: futuresAccountInfo.duoUsdt,
    kong_usdt: futuresAccountInfo.kongUsdt,
    position_config: accountMachine.positionConfig
  }
  const time = btbAs.getFormatTradeTime(moment().format('YYYY-MM-DD HH:mm'), '15m')
  await ctx.services.executeRecord.addQuoteVolumeRecord(time, {
    ...volumeUpdateInfo,
    model_str: JSON.stringify(btbAs.copyIntervalTag(quoteVolumeDetailData)),
    detail: quoteVolumeDetailData
  })
  let ifBtbWarn = false
  if (abnormalVolatilityList.length > 0) {
    ifBtbWarn = true
    const rawWarnBtb = await checkWarnBtb(time)
    if (rawWarnBtb) {
      await BtbWarnProxy.update({
        _id: rawWarnBtb._id
      }, {
        list: abnormalVolatilityList.map((v) => {
          return v.code
        }),
        raw_list: abnormalVolatilityList || [],
        time_str: time,
        time: (new Date()).getTime()
      })
    } else {
      await BtbWarnProxy.newAndSave({
        list: abnormalVolatilityList.map((v) => {
          return v.code
        }),
        raw_list: abnormalVolatilityList || [],
        time_str: time,
        time: (new Date()).getTime()
      })
    }
  }
  return {
    ...resultInfo,
    ifBtbWarn
  }
}
